package cn.edu.gxnu;

import java.io.*;
import java.net.*;
import java.util.concurrent.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 可重用的 TCP 服务器实现，支持多线程处理客户端连接。
 * <p>
 * 该类封装了 TCP 服务器的核心功能，包括：
 * <ul>
 *   <li>监听指定端口</li>
 *   <li>接受客户端连接</li>
 *   <li>为每个连接创建独立线程处理</li>
 *   <li>提供优雅的启动/停止机制</li>
 *   <li>详细的连接生命周期日志</li>
 * </ul>
 *
 * <p>通过实现 {@link ClientHandler} 接口，可以自定义客户端连接的处理逻辑。
 *
 * <p>示例用法：
 * <pre>{@code
 * // 创建 TCP 服务器实例
 * TcpServer server = new TcpServer(8080, () -> new MyCustomHandler());
 *
 * // 启动服务器
 * new Thread(server::run).start();
 *
 * // 停止服务器
 * server.stop();
 * }</pre>
 *
 * @author YourName
 * @version 1.1
 */
public class TcpServer {
    private static final DateTimeFormatter TIMESTAMP_FORMAT =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    private final int port;
    private ServerSocket serverSocket;
    private ExecutorService threadPool;
    private volatile boolean isRunning = false;
    private ClientHandlerFactory handlerFactory;

    /**
     * 日志记录器接口，允许自定义日志输出。
     */
    public interface Logger {
        void log(String message);
        void error(String message, Throwable t);
    }

    private Logger logger = new DefaultLogger();

    /**
     * 默认日志记录器实现。
     */
    private static class DefaultLogger implements Logger {
        @Override
        public void log(String message) {
            System.out.println("[" + LocalDateTime.now().format(TIMESTAMP_FORMAT) + "] " + message);
        }

        @Override
        public void error(String message, Throwable t) {
            System.err.println("[" + LocalDateTime.now().format(TIMESTAMP_FORMAT) + "] ERROR: " + message);
            if (t != null) {
                t.printStackTrace();
            }
        }
    }

    /**
     * 客户端连接处理程序接口。
     * <p>
     * 实现此接口以定义如何处理客户端连接。
     */
    public interface ClientHandler {
        /**
         * 处理客户端连接。
         *
         * @param clientSocket 客户端套接字对象
         * @param logger 日志记录器
         * @throws IOException 如果在处理过程中发生I/O错误
         */
        void handle(Socket clientSocket, Logger logger) throws IOException;
    }

    /**
     * 客户端处理程序工厂接口。
     * <p>
     * 实现此接口以创建 {@link ClientHandler} 实例。
     */
    public interface ClientHandlerFactory {
        /**
         * 创建一个新的客户端处理程序实例。
         *
         * @return 新创建的客户端处理程序
         */
        ClientHandler createHandler();
    }

    /**
     * 构造一个 TCP 服务器实例。
     *
     * @param port 要监听的端口号
     * @param handlerFactory 客户端处理程序工厂
     * @throws IllegalArgumentException 如果端口号无效（< 0 或 > 65535）
     */
    public TcpServer(int port, ClientHandlerFactory handlerFactory) {
        if (port < 0 || port > 65535) {
            throw new IllegalArgumentException("无效的端口号: " + port);
        }

        this.port = port;
        this.handlerFactory = handlerFactory;
        this.threadPool = Executors.newCachedThreadPool();
    }

    /**
     * 设置自定义日志记录器。
     *
     * @param logger 自定义日志记录器实现
     */
    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    /**
     * 启动 TCP 服务器并开始监听客户端连接。
     * <p>
     * 此方法会阻塞当前线程，直到服务器停止。
     * 通常应在单独的线程中调用此方法。
     *
     * <p>服务器启动后，会持续接受客户端连接，
     * 并为每个连接创建一个新的线程来处理。
     */
    public void run() {
        try {
            serverSocket = new ServerSocket(port);
            isRunning = true;
            logger.log("TCP服务器已启动，监听端口: " + port);

            while (isRunning) {
                Socket clientSocket = serverSocket.accept();
                String clientInfo = clientSocket.getInetAddress() + ":" + clientSocket.getPort();
                logger.log("收到新连接: " + clientInfo);

                threadPool.execute(() -> {
                    String threadName = Thread.currentThread().getName();
                    try {
                        ClientHandler handler = handlerFactory.createHandler();
                        logger.log(threadName + ": 开始处理连接 " + clientInfo);
                        handler.handle(clientSocket, logger);
                    } catch (IOException e) {
                        logger.error(threadName + ": 处理连接时出错 - " + clientInfo, e);
                    } finally {
                        closeConnection(clientSocket, clientInfo);
                    }
                });
            }
        } catch (SocketException e) {
            if (isRunning) {
                logger.error("服务器套接字异常", e);
            }
        } catch (IOException e) {
            if (isRunning) {
                logger.error("服务器I/O异常", e);
            }
        } finally {
            stop();
        }
    }

    /**
     * 安全关闭客户端连接。
     *
     * @param clientSocket 要关闭的套接字
     * @param clientInfo 客户端信息（用于日志）
     */
    private void closeConnection(Socket clientSocket, String clientInfo) {
        try {
            if (clientSocket != null && !clientSocket.isClosed()) {
                clientSocket.close();
                logger.log("连接已关闭: " + clientInfo);
            }
        } catch (IOException e) {
            logger.error("关闭连接时出错 - " + clientInfo, e);
        }
    }

    /**
     * 停止 TCP 服务器并释放所有资源。
     * <p>
     * 此方法执行以下操作：
     * <ol>
     *   <li>停止接受新连接</li>
     *   <li>关闭服务器套接字</li>
     *   <li>关闭线程池（等待现有任务完成）</li>
     * </ol>
     */
    public void stop() {
        if (!isRunning) {
            return;
        }

        isRunning = false;
        try {
            if (serverSocket != null && !serverSocket.isClosed()) {
                serverSocket.close();
            }
        } catch (IOException e) {
            logger.error("关闭服务器套接字时出错", e);
        }

        shutdownThreadPool();
        logger.log("TCP服务器已停止");
    }

    /**
     * 优雅关闭线程池。
     */
    private void shutdownThreadPool() {
        threadPool.shutdown();
        try {
            if (!threadPool.awaitTermination(30, TimeUnit.SECONDS)) {
                logger.log("线程池未能在30秒内终止，强制关闭");
                threadPool.shutdownNow();
            }
        } catch (InterruptedException e) {
            threadPool.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 检查服务器是否正在运行。
     *
     * @return 如果服务器正在运行返回 true，否则返回 false
     */
    public boolean isRunning() {
        return isRunning;
    }

    /**
     * 获取服务器监听的端口号。
     *
     * @return 服务器端口号
     */
    public int getPort() {
        return port;
    }
}